// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved.


// FILE:      AsyncRPCc.c
//
// USAGE:     AsyncRPCc /?
//
// PURPOSE:   Client side main program
//
// COMMENTS:  The Microsoft RPC AsyncRPC sample program demonstrates
//            the use of the [async] attribute and the related asynchronous
//            RPC API functions. The client will first call the asynchronous
//            function. The time it will take to run on the server side is
//            specified by the user and is passed by the asynchronous function
//            to the server as one of its parameters (asynchronous_delay).
//            The non-asynchronous routine is called next while the asynchronous one
//            is still running on the server side. The asynchronous routine can be
//            cancelled by supplying the command line option "-c".
//
//            The user can choose any of the following three types of asynchronous
//            notification methods: event, iocp, or apc.
//              
//            For more information, see MSDN online reference.
//
//

#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include "AsyncRPC.h"   // header file generated by MIDL compiler
#include "spn.h"

#define DEFAULT_NOTIFICATION "event"
#define DEFAULT_MESSAGE      "message from client"

void Usage(char * pszProgramName)
{
   fprintf_s(stderr, "USAGE:  %s\n", pszProgramName);
   fprintf_s(stderr, "          -p protocol_sequence\n");
   fprintf_s(stderr, "          -e endpoint\n");
   fprintf_s(stderr, "          -a server principal name\n");	   
   fprintf_s(stderr, "          -n network_address\n");
   fprintf_s(stderr, "          -o network_options\n");
   fprintf_s(stderr, "          -m notification_method\n");
   fprintf_s(stderr, "          -d asynchronous_delay\n");
   fprintf_s(stderr, "          -c\n");
   fprintf_s(stderr, "          -s\n\n");

   fprintf_s(stderr, "DESCRIPTION:\n");
   fprintf_s(stderr, " -p protocol_sequence:\n"
                   "   Specifies the protocol sequence (default is %s).\n",
                   DEFAULT_PROTOCOL_SEQUENCE);
   fprintf_s(stderr, " -e endpoint:\n"
                   "   Specifies the endpoint (default is %s).\n",
                   DEFAULT_ENDPOINT);
   fprintf_s(stderr, " -a server principal name:\n"
                   "   Specifies the server principal name (default is NULL).\n");
   fprintf_s(stderr, " -n network_address:\n"
                   "   Specifies the host name or IP address (default is localhost).\n");
   fprintf_s(stderr, " -o network_options:\n"
                   "   Specified the network options.\n");
   fprintf_s(stderr, " -m notification_method:\n"
                   "   Specifies the notification method used by the asynchronous function.\n"
                   "   Supported method: event, iocp, or apc (default is %s).\n",
                   DEFAULT_NOTIFICATION);
   fprintf_s(stderr, " -d asynchronous_delay:\n"
                   "   Specifies the number of seconds how long the asynchronous function\n"
                   "   will take ro run on the server (default is %u seconds).\n",
                   DEFAULT_ASYNC_DELAY);
   fprintf_s(stderr, " -c:\n"
                   "   Indicates the asynchronous call will be cancelled.\n");
   fprintf_s(stderr, " -s:\n"
                   "   Indicates the server will be shutdown.\n");
   exit(1);
}


void RPC_ENTRY APCNotificationRoutine(PRPC_ASYNC_STATE pAsync,
                                      void *Context,
                                      RPC_ASYNC_EVENT Event);


void __cdecl main(int argc, char **argv)
{
   unsigned char *pszUuid               = NULL;
   unsigned char *pszProtocolSequence   = DEFAULT_PROTOCOL_SEQUENCE;
   unsigned char *pszEndpoint           = DEFAULT_ENDPOINT;  
   unsigned char *pszSpn                = NULL; 
   unsigned char *pszNetworkAddress     = NULL;
   unsigned char *pszOptions            = NULL;
   unsigned char *pszStringBinding      = NULL;
   unsigned char *pszNotificationMethod = DEFAULT_NOTIFICATION;
   unsigned char *pszMessage            = DEFAULT_MESSAGE;
   unsigned long nAsychDelay            = DEFAULT_ASYNC_DELAY;
   unsigned long ulCode;
   unsigned long nAsychTimeOut;
   BOOL bAsychCancel = FALSE;
   BOOL bShutDownServer = FALSE;
   BOOL bIsUDP = FALSE;
   BOOL bCallFinished = TRUE;
   BOOL bAsyncCallSuccess = TRUE;
   int i, nReply;
   RPC_ASYNC_STATE Async;
   RPC_STATUS status;  
   RPC_SECURITY_QOS SecQos;
   DWORD dwResult;

   for (i = 1; i < argc; i++)
   {
      if ((*argv[i] == '-') || (*argv[i] == '/'))
      {
         switch (tolower(*(argv[i]+1)))
         {
            case 'p':
               pszProtocolSequence = argv[++i];
               break;
            case 'e':  // endpoint
               pszEndpoint = argv[++i];
               break;
            case 'n':  // network address
               pszNetworkAddress = argv[++i];
               break;
            case 'a':
               pszSpn = argv[++i];
               break;
            case 'o':
               pszOptions = argv[++i];
               break;
            case 'm':
               pszNotificationMethod = argv[++i];
               break;
            case 'd':
               if (argv[i+1][0] != '-')
                  nAsychDelay = atol(argv[++i]);
               else
                  Usage(argv[0]);
               break;
            case 'c':
               bAsychCancel = TRUE;
               break;
            case 's':
               bShutDownServer = TRUE;
               break;
            case 'h':
            case '?':
            default:
               Usage(argv[0]);
         }
      }
      else
         Usage(argv[0]);
   }

   if ((pszProtocolSequence == NULL) || (pszEndpoint == NULL) ||
       (pszNotificationMethod == NULL) || (pszMessage == NULL) ||
       (nAsychDelay < 0))
   {
      Usage(argv[0]);
   }

   // Use a convenience function to concatenate the elements of
   // the string binding into the proper sequence.
   status = RpcStringBindingCompose(pszUuid,
                                    pszProtocolSequence,
                                    pszNetworkAddress,
                                    pszEndpoint,
                                    pszOptions,
                                    &pszStringBinding);
   printf_s("RpcStringBindingCompose returned 0x%x\n", status);
   printf_s("pszStringBinding = %s\n", pszStringBinding);

   if (status)
      exit(status);

   // Set the binding handle that will be used to bind to the server.
   status = RpcBindingFromStringBinding(pszStringBinding,
                                        &AsyncRPC_ClientIfHandle);
   printf_s("RpcBindingFromStringBinding returned 0x%x\n", status);
   if (status)
      exit(status);

   status = RpcAsyncInitializeHandle(&Async, sizeof(RPC_ASYNC_STATE));
   printf_s("RpcAsyncInitializeHandle returned 0x%x\n", status);
   if (status)
      exit(status);

   Async.UserInfo = NULL;

   // user did not specify spn, construct one.
   if (pszSpn == NULL) {
	   MakeSpn(&pszSpn);
   }
   
   // Set the quality of service on the binding handle
   SecQos.Version = RPC_C_SECURITY_QOS_VERSION_1;
   SecQos.Capabilities = RPC_C_QOS_CAPABILITIES_MUTUAL_AUTH;
   SecQos.IdentityTracking = RPC_C_QOS_IDENTITY_DYNAMIC;
   SecQos.ImpersonationType = RPC_C_IMP_LEVEL_IDENTIFY;
   
   // Set the security provider on binding handle
   status = RpcBindingSetAuthInfoEx(AsyncRPC_ClientIfHandle,
									pszSpn,
									RPC_C_AUTHN_LEVEL_PKT_PRIVACY,
									RPC_C_AUTHN_GSS_NEGOTIATE,
									NULL,
									RPC_C_AUTHZ_NONE,
									&SecQos);
   
   printf_s("RpcBindingSetAuthInfoEx returned 0x%x\n", status);
   if (status) {
	   exit(status);
   }   
   
   // Detemine what type of notification method should be used
   if (_stricmp(pszNotificationMethod, "apc") == 0)
   {
      Async.NotificationType = RpcNotificationTypeApc;
      if (_stricmp(pszProtocolSequence, "ncadg_ip_udp") == 0)
         bIsUDP = TRUE;
   }
   else if (_stricmp(pszNotificationMethod, "iocp") == 0)
      Async.NotificationType = RpcNotificationTypeIoc;
   else // "event"
      Async.NotificationType = RpcNotificationTypeEvent;

   // Fill in the RPC_ASYNC_STATE structure
   switch (Async.NotificationType)
   {
      case RpcNotificationTypeApc:
         Async.u.APC.NotificationRoutine = (PFN_RPCNOTIFICATION_ROUTINE)APCNotificationRoutine;
         Async.u.APC.hThread = 0;
         // Using APC and UDP will potentially cause SleepEx() to
         // block forever. We use a user defined flag to check
         // whether the call is completed or not.
         if (bIsUDP)
            Async.UserInfo = (void*)&bCallFinished;
         break;
      case RpcNotificationTypeIoc:
         Async.u.IOC.hIOPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
         if (Async.u.IOC.hIOPort == NULL)
         {
            printf_s("CreateIoCompletionPort() failed: %d\n", GetLastError());
            exit(1);
         }
         break;
      default:  // case RpcNotificationTypeEvent:
         Async.u.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
         if (Async.u.hEvent == 0)
         {
            printf_s("CreateEvent() failed: %d\n", GetLastError());
			
            exit(1);
         }
         break;
   }

   // Call an asynchronous RPC rountine here
   RpcTryExcept
   {
      printf_s("\nCalling the remote procedure 'AsyncFunc'\n");
      AsyncFunc(&Async, AsyncRPC_ClientIfHandle, nAsychDelay);
   }
   RpcExcept(1)
   {
      ulCode = RpcExceptionCode();
      printf_s("AsyncFunc: Runtime reported exception 0x%lx = %ld\n", ulCode, ulCode);

   }
   RpcEndExcept

   // Call a synchronous routine while
   // the asynchronous routine is still running
   RpcTryExcept
   {
      printf_s("\nCalling the remote procedure 'NonAsyncFunc'\n");
      NonAsyncFunc(AsyncRPC_ClientIfHandle, pszMessage);
      fprintf_s(stderr, "You see, while 'AsyncFunc' is running asyncronously,\n"
                      "we still can send message to the server in the mean time.\n\n");
   }
   RpcExcept(( ( (RpcExceptionCode() != STATUS_ACCESS_VIOLATION) &&
                   (RpcExceptionCode() != STATUS_DATATYPE_MISALIGNMENT) &&
                   (RpcExceptionCode() != STATUS_PRIVILEGED_INSTRUCTION) &&
                   (RpcExceptionCode() != STATUS_BREAKPOINT) &&
                   (RpcExceptionCode() != STATUS_STACK_OVERFLOW) &&
                   (RpcExceptionCode() != STATUS_IN_PAGE_ERROR) &&
                   (RpcExceptionCode() != STATUS_GUARD_PAGE_VIOLATION)
                    )
                    ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH ))
   {
      ulCode = RpcExceptionCode();
     
 printf_s("NonAsyncFunc: Runtime reported exception 0x%lx = %ld\n", ulCode, ulCode);

   }
   RpcEndExcept

   if (bAsychCancel)
   {
      // nonabortive cancel
      status = RpcAsyncCancelCall(&Async, FALSE);
      printf_s("RpcAsyncCancelCall returned 0x%x\n", status);
      if (status)
         exit(status);
   }

   // should plus the synchronous call
   // time if it also takes long
   nAsychTimeOut = nAsychDelay * 1000 * 3;

   // Wait for asynchronous notification
   printf_s("Waiting for asynchronous call to finish ...\n");
   switch (Async.NotificationType)
   {
      case RpcNotificationTypeApc:
         // Over ncadg_ip_udp, the RPC calls are executed sequentially on the server side
         // according to the order they are called by the client, regardless of their
         // async attributes. When the client is using APC, we need to "manually" check
         // whether AsyncFunc() is finished or not.
         if (bIsUDP)
         {
            if (!bCallFinished)
            {
               printf_s("Asynchronous call failed\n");
               bAsyncCallSuccess = FALSE;
            }
         }
         else
         {
            dwResult = SleepEx(nAsychTimeOut, TRUE);
            if (dwResult != WAIT_IO_COMPLETION)
            {
               if (dwResult == 0)
                  printf_s("SleepEx() is timed out\n");
               else
                  printf_s("SleepEx() failed: %d\n", dwResult);

               bAsyncCallSuccess = FALSE;
            }
         }
         break;
      case RpcNotificationTypeIoc:
         if (!GetQueuedCompletionStatus(Async.u.IOC.hIOPort,
                                        &Async.u.IOC.dwNumberOfBytesTransferred,
                                        &Async.u.IOC.dwCompletionKey,
                                        &Async.u.IOC.lpOverlapped,
                                        nAsychTimeOut))
         {
            dwResult = GetLastError();

            if (dwResult == WAIT_TIMEOUT)
               printf_s("GetQueuedCompletionStatus() is timed out\n");
            else
               printf_s("GetQueuedCompletionStatus() failed: %d\n", dwResult);

            bAsyncCallSuccess = FALSE;
         }
         break;
      default:  // case RpcNotificationTypeEvent:
         dwResult = WaitForSingleObject(Async.u.hEvent, nAsychTimeOut);
         if (dwResult == WAIT_FAILED)
         {
            printf_s("WaitForSingleObject() failed: %d\n", GetLastError());
            bAsyncCallSuccess = FALSE;
         }
         else if (dwResult == WAIT_TIMEOUT)
         {
            printf_s("WaitForSingleObject() is timed out\n");
            bAsyncCallSuccess = FALSE;
         }
         break;
   }

   if (bAsyncCallSuccess)
   {
      printf_s("Calling RpcAsyncCompleteCall\n");
      status = RpcAsyncCompleteCall(&Async, &nReply);
      printf_s("RpcAsyncCompleteCall returned 0x%x\n", status);
      if (status)
         exit(status);

      printf_s("Remote procedure 'AsyncFunc' returned successfully\n\n");
   }

   // Clean up notification resources
   switch (Async.NotificationType)
   {
      case RpcNotificationTypeApc:
         break;
      case RpcNotificationTypeIoc:
         CloseHandle(Async.u.IOC.hIOPort);
         break;
      default:  // case RpcNotificationTypeEvent:
         CloseHandle(Async.u.hEvent);
         break;
   }

   if (bShutDownServer)
   {
      printf_s("Shutting down server ...\n");
      Shutdown(AsyncRPC_ClientIfHandle); // shut down the server side
   }

   // The calls to the remote procedures are complete.
   // Free the string and the binding handle.
   status = RpcStringFree(&pszStringBinding);
   printf_s("RpcStringFree returned 0x%x\n", status);
   if (status)
      exit(status);

   status = RpcBindingFree(&AsyncRPC_ClientIfHandle);
   printf_s("RpcBindingFree returned 0x%x\n", status);
   if (status)
      exit(status);

   exit(0);
}


void RPC_ENTRY APCNotificationRoutine(PRPC_ASYNC_STATE pAsync,
                            void *Context,
                            RPC_ASYNC_EVENT Event)
{
   BOOL *pbCallFinished = (BOOL*)(pAsync->UserInfo);

   printf_s("APCNotificationRoutine reported ");

   switch (Event)
   {
      case RpcCallComplete:
         printf_s("event RpcCallComplete\n");
         if (pbCallFinished != NULL)
            *pbCallFinished = TRUE;  // used for ncadg_ip_udp
         break;
      case RpcSendComplete:
         printf_s("event RpcSendComplete\n");
         break;
      case RpcReceiveComplete:
         printf_s("event RpcReceiveComplete\n");
         break;
      default:
         printf_s("event unknown\n");
         break;
   }

   return;
}


// MIDL allocate and free
void  __RPC_FAR * __RPC_USER midl_user_allocate(size_t len)
{
   return(malloc(len));
}


void __RPC_USER midl_user_free(void __RPC_FAR * ptr)
{
   free(ptr);
}
